#include <stdio.h>
#include <malloc.h>
#include <pthread.h>
#include <semaphore.h>
#include <math.h>
#include <time.h>
#include <sys/syscall.h>

#define N 10 //constant representing # of students
#define M 15 //constant representing # of books
#define T 5  //constant for the amount of time
#define TRUE 1
#define FALSE 0

/* GLOBAL VARIABLES */
typedef int boolean;


// Define book structure
typedef struct {
        int num_pages;
		boolean selected;
		struct booktype *previous;
		struct booktype *next;
} booktype;

// Declare semaphores and total_pages global variable
sem_t sem_people, sem_books, sem_pick, sem_check;
int total_pages;
booktype *first;

// Functions used 
void *student_thread(void *param);
void meet_at_the_library();
booktype* pick_a_book();
void read_book(booktype* book);
boolean share_impressions(booktype* book);
void return_book(booktype* book);
void initializeBooks(booktype* book);

int main()
{
        int i; //used for counter when creating threads
        int j; //used for counter when joining threads
		int k; //used to instantiate books linked list
	   
        //initializing total pages read and semaphores
        total_pages = 0;
        sem_init(&sem_people,0,N);
        sem_init(&sem_books,0,M);
		sem_init(&sem_pick,0,1);
		sem_init(&sem_check,0,1);
		
		/* We need to instantiate M number of books with a linked list*/
		booktype book;
		booktype *temp = (booktype*) malloc(sizeof(booktype));
		first = (booktype*) malloc(sizeof(booktype));
		first->next = &book;
		first->previous = NULL;
		book.id=1;
		book.num_pages=10;// Needs random
		book.selected=FALSE;
		book.previous=first;
		temp = first;
		
		for(k=0; k<M; k++)
		{
			booktype book2;
			book2.id=(k+2);
			book2.num_pages=10;// Needs random
			book2.selected=FALSE;
			temp = temp->next;
			temp->next = &book2;
			book2.previous = temp;
		}
	
        //creating N student threads
        pthread_t tid[N];
        pthread_attr_t pt_attribs;
		
        for (i=0; i<N; i++)
        {
                pthread_attr_init(&pt_attribs);
                pthread_create(&(tid[i]), &pt_attribs, student_thread, NULL);
        }

        if (T == 1) sleep(30);

        for (j=0; j<N; j++)
                pthread_join(tid[j], NULL);

        return 0;
}

//function for threads provided by Dr. Varsamopoulos
void *student_thread(void *param)
{
        booktype* book = (booktype*) malloc(sizeof(booktype));
        boolean done = FALSE;
        meet_at_the_library();
        do {
                book = pick_a_book();
                read_book(book);
                done = share_impressions(book);
                return_book(book);
        }while(!done);
        free(book);
        pthread_exit(0);
}

void meet_at_the_library() //This function returns after all student threads enter it
{

        // Time information
        time_t time1,time2;
        struct tm * timeinfo;
        char buffer[20];
        double dif;
		
		// Used for sem_people value
        int value;

        // Prints out current time each thread enters
        time(&time1);
        timeinfo = localtime(&time1);
        strftime(buffer,20,"%H:%M:%S ",timeinfo);
        printf("%s Thread %ld : Entering meet_at_the_library()\n",buffer,(long int) syscall(SYS_gettid));
       
        // Uses a semaphore to wait for all of the student threads to arrive
        sem_wait(&sem_people);
        sem_getvalue(&sem_people, &value);
        while (value > 0)
        {
             sleep(30);
        } 
		sem_post(&sem_people);
		
		/* There is a problem here because only one thread is leaving this function
		I believe this is the problem with the segmentation fault later on, other 
		threads are stuck here */
        
        // Prints out time each thread exits
        time(&time2);
        timeinfo = localtime(&time2);
        strftime(buffer,20,"%H:%M:%S ",timeinfo);
		printf("%s Thread %ld : Exiting meet_at_the_library() ",buffer,(long int) syscall(SYS_gettid));

        // Prints out the time difference
        dif = difftime(time2,time1);
        printf("Time taken: %f\n",dif);
}

booktype* pick_a_book()
{
        // Time infomation
        time_t time1,time2;
        struct tm * timeinfo;
        char buffer[20];
        double dif;
		booktype* temp = (booktype*) malloc(sizeof(booktype));
		
		// Variables associated with selecting a book
		int i;
		int numOfAvailableBooks; // Declare as a global variable
		
		//prints out current time & function entered
        time(&time1);
        timeinfo = localtime(&time1);
        strftime(buffer,20,"%H:%M:%S ",timeinfo);
        printf("%s Thread %ld : Entering pick_a_book() and chose book %d\n",buffer,(long int) syscall(SYS_gettid),bookSelected);
		
		sem_wait(&sem_check);
		booktype *bookSelected = (booktype*) malloc(sizeof(booktype));
		temp=first;
		while(temp->next!=NULL)
		{
			temp=temp->next;
			if(temp->selected==FALSE)
				numOfAvailableBooks++;
		}
	
		if (numOfAvailableBooks>0)
		{
			//randomly select book
			bookSelected->selected=TRUE;
		}
		
		sem_post(&sem_check);
		
        //prints out time & function exiting
        time(&time2);
        timeinfo = localtime(&time2);
        strftime(buffer,20,"%H:%M:%S ",timeinfo);
        printf("%s Thread %ld : Exiting pick_a_book() ",buffer,(long int) syscall(SYS_gettid));

        //prints out the time difference
        dif = difftime(time2,time1);
        printf("Time taken: %f\n",dif);

		//Return book selected by this specific student thread
        return bookSelected;
}

void read_book(booktype* book)
{
        // Time information
        time_t time1,time2;
        struct tm * timeinfo;
        char buffer[20];
        double dif;

        // Prints out current time & function entered
        time(&time1);
        timeinfo = localtime(&time1);
        strftime(buffer,20,"%H:%M:%S ",timeinfo);
        printf("%s Thread %ld : Entering read_book()\n",buffer,(long int) syscall(SYS_gettid));
		
		/* Each student thread can be in read_book at the same time, 
		and when each are done reading their book they will leave and 
		go to the share_impressions function */
		
		/* Figure out how long it will take each student_thread to read
		its book...exponentially distributed?*/
       
        // Prints out time & function exiting
        time(&time2);
        timeinfo = localtime(&time2);
        strftime(buffer,20,"%H:%M:%S ",timeinfo);
        printf("%s Thread %ld : Exiting read_book() ",buffer,(long int) syscall(SYS_gettid));

        // Prints out the time difference
        dif = difftime(time2,time1);
        printf("Time taken: %f\n",dif);

}

boolean share_impressions(booktype* book)
{
        // Time information
        time_t time1,time2;
        struct tm * timeinfo;
        char buffer[20];
        double dif;
		
		// Variable used with values of sem_people
        int value;

        // Prints out current time & function entered
        time(&time1);
        timeinfo = localtime(&time1);
        strftime(buffer,20,"%H:%M:%S ",timeinfo);
        printf("%s Thread %ld : Entering share_impressions()\n",buffer,(long int) syscall(SYS_gettid));

        // Waits for all of the people to arrive and totals the amount of pages read
		
        /* Figure out how long it will take threads to share_information, 
		exponentially distributed*/
		
		
        sem_wait(&sem_people);
        total_pages += book->num_pages;
        sem_getvalue(&sem_people, &value);
        while (value > 0)
        {
                sleep(30);
        }
		
		//sleep(random time based on total_pages*T)
		
		// Problem here with posting all of the semaphores 
        sem_post(&sem_people);
		total_pages=0;
		
        // Prints out time & function exiting
        time(&time2);
        timeinfo = localtime(&time2);
        strftime(buffer,20,"%H:%M:%S ",timeinfo);
        printf("%s Thread %ld : Exiting share_impressions() ",(long int) syscall(SYS_gettid));

        // Prints out the time difference
        dif = difftime(time2,time1);
        printf("Time taken: %f\n",dif);
        return TRUE;
}

void return_book(booktype* book)
{
        // Time information
        time_t time1,time2;
        struct tm * timeinfo;
        char buffer[20];
        double dif;
		int i;
		booktype* temp=(booktype*) malloc(sizeof(booktype));
		booktype* temp2=(booktype*) malloc(sizeof(booktype));
		
        // Prints out current time & function entered
        time(&time1);
        timeinfo = localtime(&time1);
        strftime(buffer,20,"%H:%M:%S ",timeinfo);
        printf("%s Thread %ld : Entering return_book()\n",buffer,(long int) syscall(SYS_gettid));
		
		sem_wait(&sem_check);
		
		// Book has been returned aka deleted
		temp=book->next;
		temp2=book->previous;
		
		temp->previous=temp2;
		temp2->next=temp;
		
		sem_post(&sem_check);
       
        // Prints out time & function exiting
        time(&time2);
        timeinfo = localtime(&time2);
        strftime(buffer,20,"%H:%M:%S ",timeinfo);
        printf("%s Thread %ld : Exiting return_book() ",buffer,(long int) syscall(SYS_gettid));

        // Prints out the time difference
        dif = difftime(time2,time1);
        printf("Time taken: %f\n",dif);

}
